
import { get_ele_left } from "@/utils/dom/get_ele_left";
import { CloseOutlined, MoreOutlined } from "@ant-design/icons";
import { ConfigProvider, Dropdown, DropDownProps, Flex, Typography } from "antd";
import { MenuItemType } from "antd/es/menu/interface";
import classNames from "classnames";
import { debounce } from "lodash";
import React, { DragEventHandler, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRefNow } from "../../../hooks/useRefNow";
import { find_parent } from "../../../utils/dom/find_parent";
import { IEditorInfo } from "../IEditorInfo";
import { TabButton } from "../TabButton";
import styles from "./style.module.scss";

export interface IHandleDropdownItemClick<T> {
  (k: ContextMenuKey, data: T | null): void
}
export function useDropdown<T>(on_click?: IHandleDropdownItemClick<T> | null): readonly [DropDownProps, React.RefObject<T | null>] {
  const ref_onClick = useRefNow(on_click);
  const ref_data = useRef<T | null>(null)
  const props = useMemo(() => {
    const items = Object.keys(ContextMenuKey)
      .map(key => {
        return {
          key: (ContextMenuKey as any)[key],
          label: key
        }
      })
    const ret: DropDownProps = {
      trigger: ['contextMenu'],
      menu: {
        items,
        onClick: (item) => ref_onClick.current?.(item.key as ContextMenuKey, ref_data.current)
      },
    }
    return ret;
  }, [])
  return [props, ref_data] as const
}
export interface ITabBarProps extends React.HTMLAttributes<HTMLDivElement> {
  infos?: IEditorInfo[];
  actived?: IEditorInfo;
  on_actived?(info: IEditorInfo): void;
  on_deleted?(info: IEditorInfo): void;
  on_tab_drag_start?(info: IEditorInfo): void;
  on_tab_drag_end?(info: IEditorInfo): void;
  on_somethings_drop?(pair: [index: number, prev: IEditorInfo | undefined, next: IEditorInfo | undefined,]): void;
  on_dropdown_item_click?: IHandleDropdownItemClick<IEditorInfo>;
}
export enum ContextMenuKey {
  关闭 = '1',
  关闭其他 = '2',
  关闭左侧全部 = '3',
  关闭右侧全部 = '4',
  关闭全部 = '5',
}


export function TabBar(props: ITabBarProps) {
  const { infos, actived,
    on_actived, on_deleted, className,
    on_tab_drag_start, on_tab_drag_end,
    onDragOver,
    onDragEnter,
    onDragLeave,
    onDrop,
    on_somethings_drop,
    on_dropdown_item_click,
    ..._p
  } = props
  const ref_infos = useRefNow(infos);
  const ref_ele_root = useRef<HTMLDivElement | null>(null)
  const ref_on_actived = useRefNow(on_actived)
  const ref_on_deleted = useRefNow(on_deleted)
  const ref_on_tab_drag_start = useRefNow(on_tab_drag_start)
  const ref_on_tab_drag_end = useRefNow(on_tab_drag_end)
  const ref_drag_count = useRef(0)
  const root_classname = classNames(styles.tab_bar, className)
  const ref_actived_delimiter = useRef<HTMLElement>(null)

  useEffect(() => {
    const ele = ref_ele_root.current;
    if (!ele) return;
    const on_resize = debounce(() => { })
    on_resize()
    const ob = new ResizeObserver(on_resize)
    ob.observe(ele)
    return () => ob.disconnect()
  }, [])
  const [dropdown_props, ref_click_item] = useDropdown<IEditorInfo>(on_dropdown_item_click);
  const ref_children_refs = useRef<(HTMLElement | null)[]>([])
  const children = useMemo(() => {
    const ret: ReactNode[] = []
    if (!infos) return ret
    for (let idx = 0; idx < infos.length + 1; idx++) {
      const info: IEditorInfo | undefined = infos[idx];
      const prev: IEditorInfo | undefined = infos[idx - 1];
      const tab_delimiter_key = `${'' + prev?.uid}#${'' + info?.uid}`
      ret.push(
        <div
          key={tab_delimiter_key}
          className={styles.tab_delimiter}
          info-index={'' + idx}
          prev-info-id={prev?.uid}
          next-info-id={info?.uid}
        />
      )
      if (idx == infos.length)
        break;
      ret.push(
        <Dropdown {...dropdown_props} onOpenChange={open => { if (open) ref_click_item.current = info }}>
          <TabButton
            on_ref={r => ref_children_refs.current[idx] = r}
            info={info}
            id={info.uid}
            key={info.uid}
            onDragStart={() => ref_on_tab_drag_start.current?.(info)}
            onDragEnd={() => ref_on_tab_drag_end.current?.(info)}
            actived={actived?.uid === info.uid}
            onMouseDown={(e) => e.button === 0 && ref_on_actived.current?.(info)}
            on_deleted={() => ref_on_deleted.current?.(info)} />
        </Dropdown>
      )
    }
    return ret;
  }, [infos, actived])

  const [need_scroll, set_need_scroll] = useState(true);
  const start_docking = () => { }
  const stop_docking = () => {
    ref_drag_count.current = 0
    const delimiter = ref_actived_delimiter.current
    if (!delimiter) return;
    delimiter.style.opacity = ''
    ref_actived_delimiter.current = null
  }

  const on_drag_enter: DragEventHandler<HTMLDivElement> = (e) => {
    onDragEnter?.(e)
    ++ref_drag_count.current
    if (ref_drag_count.current == 1) start_docking()
  }

  const on_drag_leave: DragEventHandler<HTMLDivElement> = (e) => {
    onDragLeave?.(e)
    --ref_drag_count.current
    if (ref_drag_count.current == 0) stop_docking()
  }

  const on_drag_over: DragEventHandler<HTMLDivElement> = (e) => {
    onDragOver?.(e);
    e.preventDefault()
    const root = ref_ele_root.current;
    if (!root) return;
    const dest = find_parent(e.target as HTMLElement, ele => {
      return ele === root || ele.getAttribute(TabButton.ATTR_INFO_ID)
    })
    if (!dest) return;
    if (dest === root) {
      const list = root.getElementsByClassName(styles.tab_delimiter)
      const curr = list.item(list.length - 1) as HTMLElement | null
      const prev = ref_actived_delimiter.current;
      if (prev != curr && prev) prev.style.opacity = '0';
      if (curr) curr.style.opacity = '1'
      ref_actived_delimiter.current = curr
    } else if (dest) {
      const prev = ref_actived_delimiter.current;
      const x = e.clientX - get_ele_left(dest)
      const curr = (
        (x > dest.getBoundingClientRect().width / 2) ?
          dest.nextElementSibling :
          dest.previousElementSibling
      ) as HTMLElement | null;
      if (prev != curr && prev) prev.style.opacity = '0';
      if (curr) curr.style.opacity = '1'
      ref_actived_delimiter.current = curr
    }
  }

  const on_drop: DragEventHandler<HTMLDivElement> = (e) => {
    e.preventDefault();
    onDrop?.(e)
    ref_drag_count.current = 0
    const delimiter = ref_actived_delimiter.current
    if (delimiter) {
      const index = delimiter.getAttribute('info-index')
      const a = infos?.find(v => v.uid === delimiter.getAttribute('prev-info-id'))
      const b = infos?.find(v => v.uid === delimiter.getAttribute('next-info-id'))
      on_somethings_drop?.([Number(index), a, b])
    }
    stop_docking()
  }

  useEffect(() => {
    const ele = ref_ele_root.current;
    if (!ele) return;
    const on_resize = () => set_need_scroll(ele.scrollWidth > ele.clientWidth)
    const ob = new ResizeObserver(on_resize);
    const ob2 = new MutationObserver(on_resize);
    ob.observe(ele);
    ob2.observe(ele, {
      childList: true,
      attributes: true,
      characterData: true,
      subtree: true
    })
    return () => {
      ob.disconnect();
      ob2.disconnect();
    }
  }, [])

  const [out_of_range_items, set_out_of_range_items] = useState<MenuItemType[]>([])

  const update_out_of_range_items = useCallback(() => {
    const ele = ref_ele_root.current
    if (!ele) return;
    const next: MenuItemType[] = []
    for (let i = 0; i < ref_children_refs.current.length; i++) {
      const child = ref_children_refs.current[i];
      const info = ref_infos.current?.[i];
      if (!child || !info) continue;
      const viewport_left = ele.scrollLeft + 30;
      const viewport_right = ele.scrollLeft + ele.clientWidth - 30
      const child_left = child.offsetLeft;
      const child_right = child.offsetLeft + child.clientWidth;
      if (child_right + child.clientWidth < viewport_left || child_left > viewport_right) {
        next.push({
          key: child.id,
          label: (
            <Flex
              style={{ display: 'flex' }}
              gap={10}
              justify='space-between'
              align='center'
              onClick={() => ref_on_actived.current?.(info)}>
              <Typography.Text>{child.getAttribute('info-title')}</Typography.Text>
              <CloseOutlined
                onPointerDown={e => e.stopPropagation()}
                onPointerUp={e => e.stopPropagation()}
                onClick={e => {
                  e.stopPropagation();
                  ref_on_deleted.current?.(info);
                }} />
            </Flex>
          ),
          onClick: () => child.click()
        })
      }
    }
    set_out_of_range_items(prev => {
      const changed =
        prev.map(v => `${v.key}_${v.label}`).join('##') !==
        next.map(v => `${v.key}_${v.label}`).join('##')
      return changed ? next : prev;
    })
  }, [])

  useEffect(() => {
    if (need_scroll) update_out_of_range_items()
    else set_out_of_range_items([])
  }, [need_scroll, infos])

  return <>
    <div
      className={root_classname}
      {..._p}
      ref={ref_ele_root}
      style={{ marginRight: out_of_range_items.length ? 15 : void 0 }}
      onScroll={update_out_of_range_items}
      onDragEnter={on_drag_enter}
      onDragLeave={on_drag_leave}
      onDragOver={on_drag_over}
      onDrop={on_drop}
      onWheel={need_scroll ? e => {
        if (!e.shiftKey && e.deltaY && !e.deltaX)
          ref_ele_root.current?.scrollBy({ behavior: 'smooth', left: e.deltaY })
      } : void 0} >
      <ConfigProvider wave={{ disabled: true }}>
        {children}
      </ConfigProvider>
    </div>
    {
      out_of_range_items?.length ?
        <Dropdown menu={{ items: out_of_range_items }}>
          <button className={styles.tab_bar_more_button}>
            <MoreOutlined />
          </button>
        </Dropdown> : null
    }
  </>
}


